package com.jacky.iot;

import com.jacky.iot.resources.MulticastHelloResource;
import io.netty.channel.unix.Errors;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.config.CoapConfig;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.server.resources.MyIpResource;
import org.eclipse.californium.elements.UDPConnector;
import org.eclipse.californium.elements.UdpMulticastConnector;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.config.UdpConfig;
import org.eclipse.californium.elements.util.NetworkInterfacesUtil;
import org.eclipse.californium.elements.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.net.*;
import java.util.concurrent.TimeUnit;

public class MulticastServer {

    private static final boolean LOOP_BACK = false;
    private static final File CONFIG_FILE = new File("Californium3MulticastServer.properties");
    private static final String CONFIG_HEADER = "Californium CoAP Properties file for multicast server";

    static {
        CoapConfig.register();
        UdpConfig.register();
    }

    public static void main(String[] args) {
        Configuration config = Configuration.createWithFile(CONFIG_FILE, CONFIG_HEADER, DEFAULT_DEFINITIONS);
        Configuration.setStandard(config);
        Integer uniCastPort = config.get(CoapConfig.COAP_PORT);
        int multicastPort = uniCastPort;

        args = new String[2];
        args[0] = "5683";
        args[1] = "5684";

        switch (args.length) {
            default:
                System.out.println("usage: MulticastTestServer [uniCast-port [multicast-port]]");
            case 2:
                multicastPort = Integer.parseInt(args[1]);
            case 1:
                uniCastPort = Integer.parseInt(args[0]);
            case 0:
        }
        CoapServer server = new CoapServer(config);
        createEndpoints(server, uniCastPort, multicastPort, config);
        server.add(new MulticastHelloResource());
        server.add(new MyIpResource(MyIpResource.RESOURCE_NAME, true));
        server.start();
    }

    private static void createEndpoints(CoapServer server, int uniCastPort, int multicastPort, Configuration config) {
        NetworkInterface networkInterface = NetworkInterfacesUtil.getMulticastInterface();
        if (networkInterface == null) {
            System.out.println("No multicast network-interface found");
            throw new Error("No multicast network-interface found");
        }

        System.out.println("Multicast Network Interface:" + networkInterface.getDisplayName());

        UdpMulticastConnector.Builder builder = new UdpMulticastConnector.Builder();
        if (NetworkInterfacesUtil.isAnyIpv6()) {
            Inet6Address ipv6 = NetworkInterfacesUtil.getMulticastInterfaceIpv6();
            System.out.println("Multicast: IPv6 Network Address:" + StringUtil.toString(ipv6));
            UDPConnector udpConnector = new UDPConnector(new InetSocketAddress(ipv6, uniCastPort), config);
            udpConnector.setReuseAddress(true);
            CoapEndpoint endpoint = new CoapEndpoint.Builder().setConfiguration(config).setConnector(udpConnector).build();
            builder = new UdpMulticastConnector.Builder().setLocalAddress(CoAP.MULTICAST_IPV6_SITELOCAL, multicastPort)
                    .addMulticastGroup(CoAP.MULTICAST_IPV6_SITELOCAL, networkInterface).setConfiguration(config);
            createReceiver(builder, udpConnector);

            server.addEndpoint(endpoint);
            System.out.println("IPv6 - multicast");
        }

        if (NetworkInterfacesUtil.isAnyIpv4()) {
            Inet4Address ipv4 = NetworkInterfacesUtil.getMulticastInterfaceIpv4();
            System.out.println("Multicast: IPv4 Network Address:" + StringUtil.toString(ipv4));
            UDPConnector udpConnector = new UDPConnector(new InetSocketAddress(ipv4, uniCastPort), config);
            udpConnector.setReuseAddress(true);
            CoapEndpoint endpoint = new CoapEndpoint.Builder().setConfiguration(config).setConnector(udpConnector).build();
            builder = new UdpMulticastConnector.Builder().setLocalAddress(CoAP.MULTICAST_IPV4, multicastPort)
                    .addMulticastGroup(CoAP.MULTICAST_IPV4, networkInterface).setConfiguration(config);
            createReceiver(builder, udpConnector);

            Inet4Address broadcast = NetworkInterfacesUtil.getBroadcastIpv4();
            if (broadcast != null) {
                builder = new UdpMulticastConnector.Builder().setLocalAddress(broadcast, multicastPort)
                        .setConfiguration(config);
                createReceiver(builder, udpConnector);
            }
            server.addEndpoint(endpoint);
            System.out.println("IPv4 - multicast");
        }
        UDPConnector udpConnector = new UDPConnector(new InetSocketAddress(InetAddress.getLoopbackAddress(), uniCastPort), config);
        udpConnector.setReuseAddress(true);
        CoapEndpoint endpoint = new CoapEndpoint.Builder().setConfiguration(config).setConnector(udpConnector).build();
        server.addEndpoint(endpoint);
        System.out.println("loop back");
    }

    private static void createReceiver(UdpMulticastConnector.Builder builder, UDPConnector connector) {
        UdpMulticastConnector multiConnector = builder.setMulticastReceiver(true).build();
        multiConnector.setLoopbackMode(LOOP_BACK);
        try {
            multiConnector.start();
        } catch (IOException ex) {
            // windows下多播绑定好像有问题
            if (builder.getLocalAddress().getAddress().isMulticastAddress()) {
                int port = builder.getLocalAddress().getPort();
                builder.setLocalPort(port);
                multiConnector = builder.build();
                multiConnector.setLoopbackMode(LOOP_BACK);

                try {
                    multiConnector.start();
                } catch (IOException e) {
                    e.printStackTrace();
                    multiConnector = null;
                }
            } else {
                ex.printStackTrace();
                multiConnector = null;
            }
            if (multiConnector != null && connector != null) {
                connector.addMulticastReceiver(multiConnector);
            }
        }
    }

    private static Configuration.DefinitionsProvider DEFAULT_DEFINITIONS = new Configuration.DefinitionsProvider() {
        @Override
        public void applyDefinitions(Configuration config) {
            config.set(CoapConfig.LEISURE, 2, TimeUnit.SECONDS);
        }
    };


}
